package com.atguigu.paymentdemo.service.impl;

import com.atguigu.paymentdemo.config.WxPayConfig;
import com.atguigu.paymentdemo.entity.OrderInfo;
import com.atguigu.paymentdemo.entity.RefundInfo;
import com.atguigu.paymentdemo.enums.OrderStatus;
import com.atguigu.paymentdemo.enums.wxpay.WxApiType;
import com.atguigu.paymentdemo.enums.wxpay.WxNotifyType;
import com.atguigu.paymentdemo.service.OrderInfoService;
import com.atguigu.paymentdemo.service.PaymentInfoService;
import com.atguigu.paymentdemo.service.RefundInfoService;
import com.atguigu.paymentdemo.service.WxPayService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.gson.Gson;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;


@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService {


    @Resource
    private WxPayConfig wxPayConfig;

    @Resource
    private CloseableHttpClient wxPayClient;

    @Resource
    private OrderInfoService orderInfoService;

    @Resource
    private PaymentInfoService paymentInfoService;

    @Resource
    private RefundInfoService refundsInfoService;

    private final ReentrantLock lock = new ReentrantLock();


    @Override
    public void processOrder(Map<String, Object> map) throws GeneralSecurityException {
        log.info("收到微信支付回调，开始处理订单");

        //解密
        String plainText = decryptFromResource(map);

        //将明文转为map
        Gson gson = new Gson();
        Map hashMap = gson.fromJson(plainText, HashMap.class);
        //获取交易订单号
        String out_trade_no = (String) hashMap.get("out_trade_no");

        //加锁，处理并发问题
        if (lock.tryLock()) {
            try {
                //处理重复通知，对于微信重复通知的情况，如果订单已经处理，那么就直接return，（保证接口调用幂等）
                String status = orderInfoService.getOrderStatus(out_trade_no);
                if (!status.equals(OrderStatus.NOTPAY.getType())) {
                    return;
                }

                //更新订单状态
                this.updateStatusByNo(out_trade_no, OrderStatus.SUCCESS.getType());

                //记录支付日志
                paymentInfoService.createPaymentInfo(plainText);
            } finally {
                //释放锁
                lock.unlock();
            }
        }
    }

    /**
     * 解密报文
     *
     * @param map
     * @return
     */
    private String decryptFromResource(Map<String, Object> map) throws GeneralSecurityException {
        log.info("密文解密");
        Map<String, String> resourceMap = (Map<String, String>) map.get("resource");

        String ciphertext = resourceMap.get("ciphertext");
        String nonce = resourceMap.get("nonce");
        String associatedData = resourceMap.get("associated_data");
        AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
                nonce.getBytes(StandardCharsets.UTF_8),
                ciphertext);
        log.info("明文=====> {}", plainText);
        return plainText;

    }

    /**
     * 下单方法，并调用微信接口获得支付二维码图片链接
     *
     * @param productId
     * @return code_url，订单号
     * @throws IOException
     * @throws GeneralSecurityException
     */
    @Override
    public Map<String, Object> nativePay(Long productId) throws IOException {
        //生成订单
        log.info("生成订单");
        //如果库里已经有了，并且未支付那么就直接使用库里的订单，保证接口幂等
        OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId);
        log.info("调用统一下单API");
        if (orderInfo != null && !StringUtils.isEmpty(orderInfo.getCodeUrl())) {
            //如果订单信息里面已经有codeURL了，直接返回，避免用户频繁点击，频繁请求微信服务器获取codeURL;
            //TODO CodeURL有效期为两天，如果当前保存的二维码过期需要重新获取
            //TODO 判断CodeURL是否过期
            HashMap<String, Object> map1 = new HashMap<>();
            map1.put("codeUrl", orderInfo.getCodeUrl());
            map1.put("orderNumber", orderInfo.getOrderNo());
            return map1;
        }
        //调用统一下单API
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
        // 请求body参数
        Gson gson = new Gson();
        Map map = new HashMap();
        map.put("appid", wxPayConfig.getAppid());
        map.put("mchid", wxPayConfig.getMchId());
        map.put("description", orderInfo.getTitle());
        map.put("out_trade_no", orderInfo.getOrderNo());
        map.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));
        Map amountMap = new HashMap();
        amountMap.put("total", orderInfo.getTotalFee());
        amountMap.put("currency", "CNY");
        map.put("amount", amountMap);
        //将map转换为json
        String reqdata = gson.toJson(map);
        log.info("请求参数：" + reqdata);
        StringEntity entity = new StringEntity(reqdata, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        //自动签名验签
        CloseableHttpResponse response = wxPayClient.execute(httpPost);

        try {
            String result = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) { //处理成功
                log.info("成功,返回结果 = " + result);
            } else if (statusCode == 204) { //处理成功，无返回Body
                log.info("成功");
            } else {
                log.info("Native下单失败，响应码 = " + statusCode + ",返回结果 = " + result);
                throw new IOException("request failed");
            }

            HashMap<String, String> resultMap = gson.fromJson(result, HashMap.class);
            //二维码
            String code_url = resultMap.get("code_url");
            //保存支付二维码
            orderInfoService.saveCodeUrl(orderInfo.getOrderNo(), code_url);
            HashMap<String, Object> map1 = new HashMap<>();
            map1.put("codeUrl", code_url);
            map1.put("orderNo", orderInfo.getOrderNo());
            return map1;
        } finally {
            response.close();
        }
    }

    @Override
    public void updateStatusByNo(String orderNo, String status) {
        QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("order_no", orderNo);
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setOrderStatus(status);
        orderInfoService.update(orderInfo, queryWrapper);
    }

    /**
     * 退款
     *
     * @param orderNo
     * @param reason
     * @throws IOException
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void refund(String orderNo, String reason) throws Exception {

        log.info("创建退款单记录");
        //根据订单编号创建退款单
        RefundInfo refundsInfo = refundsInfoService.createRefundByOrderNo(orderNo, reason);

        log.info("调用退款API");

        //调用统一下单API
        String url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());
        HttpPost httpPost = new HttpPost(url);

        // 请求body参数
        Gson gson = new Gson();
        Map paramsMap = new HashMap();
        paramsMap.put("out_trade_no", orderNo);//订单编号
        paramsMap.put("out_refund_no", refundsInfo.getRefundNo());//退款单编号
        paramsMap.put("reason", reason);//退款原因
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款通知地址

        Map amountMap = new HashMap();
        amountMap.put("refund", refundsInfo.getRefund());//退款金额
        amountMap.put("total", refundsInfo.getTotalFee());//原订单金额
        amountMap.put("currency", "CNY");//退款币种
        paramsMap.put("amount", amountMap);

        //将参数转换成json字符串
        String jsonParams = gson.toJson(paramsMap);
        log.info("请求参数 ===> {}" + jsonParams);

        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");//设置请求报文格式
        httpPost.setEntity(entity);//将请求报文放入请求对象
        httpPost.setHeader("Accept", "application/json");//设置响应报文格式

        //完成签名并执行请求，并完成验签
        CloseableHttpResponse response = wxPayClient.execute(httpPost);

        try {

            //解析响应结果
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("成功, 退款返回结果 = " + bodyAsString);
            } else if (statusCode == 204) {
                log.info("成功");
            } else {
                throw new RuntimeException("退款异常, 响应码 = " + statusCode + ", 退款返回结果 = " + bodyAsString);
            }

            //更新订单状态
            this.updateStatusByNo(orderNo, OrderStatus.REFUND_PROCESSING.getType());

            //更新退款单
            refundsInfoService.updateRefund(bodyAsString);

        } finally {
            response.close();
        }
    }


    /**
     * 用户取消订单
     *
     * @param orderNo
     */
    @Override
    public void cancelOrders(String orderNo) throws Exception {

        //调用微信的关单接口
        this.closeOrder(orderNo);

        //更新商户端的订单状态
        this.updateStatusByNo(orderNo, OrderStatus.CANCEL.getType());
    }

    /**
     * 查询微信第三方订单交易状态
     *
     * @param orderNo
     * @return
     */
    @Override
    public String queryOrderByOrderNo(String orderNo) throws Exception {
        log.info("查单接口调用=====> {}", orderNo);
        //TODO
        String url = wxPayConfig.getDomain().concat(String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderNo).concat("?mchid=").concat(wxPayConfig.getMchId()));
        log.info("路径为： {}", url);
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");
        //完成签名并执行请求
        //自动签名验签
        CloseableHttpResponse response = wxPayClient.execute(httpGet);
        try {
            String result = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) { //处理成功
                log.info("成功,返回结果 = " + result);
            } else if (statusCode == 204) { //处理成功，无返回Body
                log.info("成功");
            } else {
                log.info("查询失败，响应码 = " + statusCode + ",返回结果 = " + result);
                throw new IOException("request failed");
            }
            return result;
        } finally {
            response.close();
        }
    }


    /**
     * 关单接口的调用
     *
     * @param orderNo
     */
    private void closeOrder(String orderNo) throws Exception {

        log.info("关单接口的调用，订单号=======> {}", orderNo);
        String url = wxPayConfig.getDomain().concat(String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderNo));
        HttpPost httpPost = new HttpPost(url);

        //组装json请求体
        Gson gson = new Gson();
        HashMap<String, String> paramsMap = new HashMap<>();
        paramsMap.put("mchid", wxPayConfig.getMchId());
        //将map转为json
        String json = gson.toJson(paramsMap);
        log.info("请求参数=======> {}", json);

        StringEntity entity = new StringEntity(json, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        //自动签名验签
        CloseableHttpResponse response = wxPayClient.execute(httpPost);
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) { //处理成功
                log.info("成功关单200");
            } else if (statusCode == 204) { //处理成功，无返回Body
                log.info("成功关单204");
            } else {
                log.info("Native关单失败，响应码 = " + statusCode);
                throw new Exception("request failed");
            }
        } finally {
            response.close();
        }
    }
}
